package la.iok.hzsvn.lewin.movie.cloud.service;

import la.iok.hzsvn.lewin.cloud.invoker.service.RestServiceInvoker;
import la.iok.hzsvn.lewin.movie.cloud.config.RedisKey;
import la.iok.hzsvn.lewin.movie.cloud.entity.LoginRecord;
import la.iok.hzsvn.lewin.movie.cloud.entity.User;
import la.iok.hzsvn.lewin.movie.cloud.entity.WxUserBinding;
import la.iok.hzsvn.lewin.movie.cloud.model.*;
import la.iok.hzsvn.lewin.movie.core.exception.ErrorCode;
import la.iok.hzsvn.lewin.movie.core.model.UserVo;
import la.iok.hzsvn.redis.service.RedisService;
import la.iok.hzsvn.share.annotations.NotNull;
import la.iok.hzsvn.share.response.SingleDataResponse;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static la.iok.hzsvn.lewin.movie.core.exception.ErrorCode.INVALID_USER;

@Service
@EnableConfigurationProperties(WxMiniProperties.class)
public class WxInvokerImpl implements WxInvoker {
    private final WxUserBindingService wxUserBindingService;
    private final UserService userService;
    private final RestServiceInvoker restServiceInvoker;
    private final LoginRecordService loginRecordService;
    private final RedisService redisService;
    private final RedisKey redisKey;
    private final WxRestService wxRestService;

    public WxInvokerImpl(WxUserBindingService wxUserBindingService,
                         UserService userService,
                         RestServiceInvoker restServiceInvoker,
                         LoginRecordService loginRecordService,
                         RedisService redisService,
                         RedisKey redisKey, WxRestService wxRestService) {
        this.wxUserBindingService = wxUserBindingService;
        this.userService = userService;
        this.restServiceInvoker = restServiceInvoker;
        this.loginRecordService = loginRecordService;
        this.redisService = redisService;
        this.redisKey = redisKey;
        this.wxRestService = wxRestService;
    }

    @NotNull
    @Override
    public SingleDataResponse<TokenInfo> code2Session(String code, UserDeviceParam device, Integer client) {
        code = ErrorCode.PROPERTY_CHECK_ERROR.assertNotBlank(code, "code", "code不能为空");
        Code2SessionRes res = wxRestService.code2Session(code, client);
        ErrorCode.WX_INVOKER_ERROR.assertNotNull(res,"jscode2session返回空");
        ErrorCode.WX_INVOKER_ERROR.assertTrue(res.success(),()->new Object[]{String.format("errcode:%d,errmsg:%s",res.getErrorCode(),res.getErrorMessage())});
        ErrorCode.WX_INVOKER_ERROR.assertNotBlank(res.getUnionID(),"返回unionID为空");
        ErrorCode.WX_INVOKER_ERROR.assertNotBlank(res.getOpenID(),"返回openID为空");
        //根据unionId查找关联的用户,然后去授权服务器拿token
        WxUserBinding binding = wxUserBindingService.select(res.getUnionID(), res.getOpenID());
        Long bindingId;
        if(binding == null){
            // 查询是否有其他小程序的绑定结果,如果有,使用那个绑定结果并拿到手机号
            binding = wxUserBindingService.select(res.getUnionID(), null);
            if(binding == null){
                return buildUserNotBindResponse(res);
            }else{
                // 保存新的微信绑定信息,使用之前绑定的手机号
                bindingId = wxUserBindingService.saveOrUpdate(res, binding.getPhoneNumber());
            }
        }else{
            bindingId = binding.getId();
        }
        User user = userService.select(binding.getPhoneNumber());
        if(user == null){
            return buildUserNotBindResponse(res);
        }
        TokenInfo token = userToken(device, bindingId, res, user);
        return new SingleDataResponse<>(token);
    }

    private TokenInfo userToken(UserDeviceParam device, Long userBindingId, Code2SessionRes res, User user) {
        INVALID_USER.assertEqual(UserVo.STATUS_NORMAL, user.getDisabled());
        Map<String,String> params = new HashMap<>();
        params.put("username", user.getPhoneNumber());
        params.put("password", user.getPassword());
        params.put("grantType","password");
        params.put("clientId","lewin");
        params.put("clientSecret","lwp2wd");
        String bindingKey = redisKey.phoneUserBindingKey(user.getPhoneNumber());
        redisService.set(bindingKey, userBindingId);
        //保存5分钟。这里同一个用户（手机号），不能同时登录不同的小程序，不然可能会映射到后面那个小程序的绑定关系。但是如果一个小程序中登录成功并拿到token之后，另一个小程序再用同一个手机号登录，是可以的
        redisService.expire(bindingKey,5,TimeUnit.MINUTES);
        TokenInfo token = restServiceInvoker.postForObject("authserver","/oauth/token?username={username}&password={password}&grant_type={grantType}&client_id={clientId}&client_secret={clientSecret}",null, TokenInfo.class,params);
        loginRecordService.save(user, device, LoginRecord.LOGIN_TYPE_WX_MINI_PROGRAM);
        if(token != null){
            String key = redisKey.wxUserSessionInfoKey(userBindingId);
            redisService.hash(key, res);
            redisService.expire(key,30,TimeUnit.DAYS);
            token.setOpenID(res.getOpenID());//告诉前端，但是没有写入到token字符串里面
        }
        return token;
    }

    private SingleDataResponse<TokenInfo> buildUserNotBindResponse(Code2SessionRes res) {
        //未绑定,需要先绑定
        String sessionId = UUID.randomUUID().toString();
        String key = redisKey.wxBindingSessionKey(sessionId);
        redisService.hash(key, res);
        redisService.expire(key,5, TimeUnit.MINUTES);
        TokenInfo token = new TokenInfo();
        token.setSessionID(sessionId);
        token.setOpenID(res.getOpenID());
        SingleDataResponse<TokenInfo> sdr = new SingleDataResponse<>(ErrorCode.USER_NOT_BINDING.message(), ErrorCode.USER_NOT_BINDING.getCode());
        sdr.setData(token);
        return sdr;
    }

    @Override
    public TokenInfo phoneNumber(@NotNull WxLoginParam param) {
        String code = ErrorCode.PROPERTY_CHECK_ERROR.assertNotBlank(param.getCode(), "code", "code不能为空");
        String sessionID = ErrorCode.PROPERTY_CHECK_ERROR.assertNotBlank(param.getSessionID(),"sessionID","sessionID不能为空");
        UserPhoneNumberRes res = wxRestService.phoneNumber(code,param.getClient());
        ErrorCode.WX_INVOKER_ERROR.assertNotNull(res,"查询用户手机号返回空");
        ErrorCode.WX_INVOKER_ERROR.assertTrue(res.success(),()->new Object[]{String.format("errcode:%d,errmsg:%s",res.getErrorCode(),res.getErrorMessage())});
        ErrorCode.WX_INVOKER_ERROR.assertNotNull(res.getPhoneInfo(),"查询用户手机号响应中未包含手机号信息");
        ErrorCode.WX_INVOKER_ERROR.assertNotBlank(res.getPhoneInfo().getPhoneNumber(),"查询用户手机号得到手机号为空");
        Code2SessionRes code2SessionRes = redisService.hash(redisKey.wxBindingSessionKey(sessionID),Code2SessionRes.class);
        ErrorCode.PROPERTY_CHECK_ERROR.assertNotNull(code2SessionRes,"调用顺序","需要先登录获取用户微信身份");
        Long id = userService.wxSave(res.getPhoneInfo().getPhoneNumber());
        Long bindingId = wxUserBindingService.saveOrUpdate(code2SessionRes,res.getPhoneInfo().getPhoneNumber());
        return userToken(param.getDevice(), bindingId, code2SessionRes,userService.select(id));
    }
}
